package com.zb.excel.importExcel.listener;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.zb.constant.CodeConstant;
import com.zb.constant.RedisImportDataConstant;
import com.zb.entity.*;
import com.zb.excel.CodeExcelImport;
import com.zb.excel.OrderExcelImport;
import com.zb.extend.CodeLogExtend;
import com.zb.redis.RedisUtil;
import com.zb.service.ZjCodeBatchService;
import com.zb.service.ZjCodeService;
import com.zb.service.impl.WorkServiceLog;
import com.zb.utils.CodeUtil;
import com.zb.utils.RandomUtil;
import com.zb.utils.TransactionUtil;
import com.zb.utils.ZbExcelFieldUtil;
import com.zb.view.ViewGoods;
import com.zb.websocket.server.ProgressWebSocketServer;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;

import java.util.*;
import java.util.regex.Pattern;

/**
 * @Author WEN
 * @Date 2024/3/16 0016
 **/
public class CodeExcelImportListener implements ReadListener<CodeExcelImport> {
    private static final Logger logger = LoggerFactory.getLogger(CodeExcelImportListener.class);
    private static int BATCH_COUNT = 300; // 每批次执行插入数据库数量
    private boolean isFirstSend; // 是否首次发送进度
    private boolean isTwoSend; // 是否二次发送进度
    private boolean isEndSend; // 是否最后一次发送进度
    private int excelTotalRowCount; // Excel总数量（此包含空行的数量）
    private int totalCount; // 导入的总数量
    private int batch_no; // 导入的批次号
    private long currentExcuteCount; // 当前执行的数量
    private Long codeBatchId; // 批次ID
    private Long codebatchGoodsId; // 批次商品ID
    private List<CodeExcelImport> importFailt;
    private StringBuffer message;
    // 校验数据重复的缓存key
    private String check_data_repeat_key;
    private String code_batch_goods_data_key; // 批次编码商品的缓存Key，表示一个Excel导入算一个批次
    private String CACHE_IMPORT_DATA_REDIS_KEY;

    private String progressUuid; // 进度uuid识别码
    private AccounLoginInfo loginInfo;
    private ZjCodeSet codeSet; // 编码设置

    private PlatformTransactionManager transactionManager;
    private ZjCodeBatchService codeBatchService;
    private ZjCodeService codeService;
    private List<ZjCodeBatch> addCodeBatchList;
    private List<ZjCodeBatch> updateCodeBatchList;
    private List<ZjCodeBatchGoods> addCodeBatchGoodsList;
    private List<ZjCodeBatchGoods> updateCodeBatchGoodsList;
    private List<ZjCode> codeList;
    private List<ZjCodeInfo> codeInfoList;
    private List<ZjCodeLog> codeLogList;


    public CodeExcelImportListener(String progressUuid, AccounLoginInfo loginInfo, ZjCodeSet codeSet,
                                   PlatformTransactionManager transactionManager, ZjCodeBatchService codeBatchService, ZjCodeService codeService) {
        this.isFirstSend = true;
        this.isTwoSend = true;
        this.isEndSend = false;
        this.excelTotalRowCount = 0;
        this.totalCount = 0;
        this.batch_no = 0;
        this.currentExcuteCount = 0;
        this.importFailt = new ArrayList<>();
        this.message = new StringBuffer();
        this.check_data_repeat_key = RedisImportDataConstant.SECURITY_CODE_IMPORT + "check-code-data-repeat:" + loginInfo.getCustAccId() + ":" + progressUuid;
        this.code_batch_goods_data_key = RedisImportDataConstant.SECURITY_CODE_IMPORT + "code-batch-goods-data:" + loginInfo.getCustAccId() + ":" + progressUuid;
        this.CACHE_IMPORT_DATA_REDIS_KEY = RedisImportDataConstant.SECURITY_CODE_IMPORT + loginInfo.getCustAccId() + progressUuid;

        this.progressUuid = progressUuid;
        this.loginInfo = loginInfo;
        this.codeSet = codeSet;

        this.transactionManager = transactionManager;
        this.codeBatchService = codeBatchService;
        this.codeService = codeService;
        initData();
    }

    private void initData() {
        this.addCodeBatchList = new ArrayList<>();
        this.updateCodeBatchList = new ArrayList<>();
        this.addCodeBatchGoodsList = new ArrayList<>();
        this.updateCodeBatchGoodsList = new ArrayList<>();
        this.codeList = new ArrayList<>();
        this.codeInfoList = new ArrayList<>();
        this.codeLogList = new ArrayList<>();
    }

    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
        this.excelTotalRowCount = context.readSheetHolder().getApproximateTotalRowNumber();
        this.totalCount = this.excelTotalRowCount -2;
        if (this.isFirstSend) {
            this.message.append("数据正在解析中，请稍等...").append("</br>");
            ProgressWebSocketServer.send(this.progressUuid, false,  new ArrayList(), this.message.toString());
            this.isFirstSend = false;
        }
    }

    @Override
    public void invoke(CodeExcelImport codeExcelImport, AnalysisContext analysisContext) {
        if (this.isTwoSend) {
            this.message.append("数据解析成功，正在导入中..").append("</br>");
            ProgressWebSocketServer.send(this.progressUuid, false,  new ArrayList(), this.message.toString());
            this.isTwoSend = false;
        }
        this.currentExcuteCount++;
        analysisContext.readWorkbookHolder().setIgnoreEmptyRow(false);
        int rowNo = analysisContext.readRowHolder().getRowIndex() + 1; // Excel行号
        if (ZbExcelFieldUtil.isLineNullValue(codeExcelImport)) {
            this.totalCount--;
            return;
        }
        // 校验字段的空值 & 字段长度值
        if (checkEmpty(rowNo, codeExcelImport)) {
            sendMessage();
            return;
        }
        // 校验数据是否重复
        if (checkDataRepeat(rowNo, codeExcelImport)) {
            sendMessage();
            return;
        }
        Map<String, Object> mapKey = getCacheData();
        // 商品
        if (null == mapKey.get("goods_model_" + codeExcelImport.getType() + "_" + codeExcelImport.getGoodsModel())) {
            this.message.append("E").append(rowNo).append("单元 商品型号不存在系统中，请检查后重新导入").append("</br>");
            this.importFailt.add(codeExcelImport);
            sendMessage();
           return;
        }
        ViewGoods viewGoods = JSON.parseObject(JSON.toJSONString(mapKey.get("goods_model_spec_" + codeExcelImport.getType() + "_" + codeExcelImport.getGoodsModel() +"_"+ codeExcelImport.getGoodsSpec())), ViewGoods.class);
        if (null == viewGoods) {
            this.message.append("F").append(rowNo).append("单元 商品规格与商品型号不匹配或数据不存在，请检查后重新导入").append("</br>");
            this.importFailt.add(codeExcelImport);
            sendMessage();
            return;
        }
        // 编码不为空
        if (StringUtils.isNotBlank(codeExcelImport.getCode()) && null != this.codeService.existBySecurityCode(this.loginInfo.getCustAccId(), codeExcelImport.getCode())) {
            this.message.append("A").append(rowNo).append("单元 一物一码/SN码已存在系统中，禁止重复导入或创建！").append("</br>");
            this.importFailt.add(codeExcelImport);
             sendMessage();
            return;
        }
        // 创建编码
        setCodeBatch(viewGoods, codeExcelImport);
        setCode(viewGoods, codeExcelImport);
        if (this.codeList.size() >= BATCH_COUNT) {
            excuteSave();
        }
        sendMessage();
    }

    /**
     * 编码批次数据
     * @param viewGoods
     * @param codeExcelImport
     */
    private void setCodeBatch(ViewGoods viewGoods, CodeExcelImport codeExcelImport) {
        Object byKey = RedisUtil.getByKey(code_batch_goods_data_key);
        Map<String, Object> codeBatchGoodstMap = null;
        if (null == byKey) {
            codeBatchGoodstMap = new HashMap<>();
        } else {
            codeBatchGoodstMap = JSON.parseObject((String) byKey, HashMap.class);
        }
        ZjCodeBatch codeBatch = JSON.parseObject(JSON.toJSONString(codeBatchGoodstMap.get("code_batch")), ZjCodeBatch.class);
        if (null == codeBatch) {
            codeBatch = new ZjCodeBatch();
            codeBatch.setId(IdWorker.getId());
            codeBatch.setCustAccId(this.loginInfo.getCustAccId());
            codeBatch.setType(1);
            codeBatch.setName("Excel导入一物一码");
            codeBatch.setCode((StringUtils.isBlank(this.codeSet.getBatchPrefix()) ? "" : this.codeSet.getBatchPrefix()) + RandomUtil.dateRandom14());
            codeBatch.setQty(1);
            codeBatch.setCodeSource(CodeConstant.CODE_SOURCE_3);
            codeBatch.setDisabled(false);
            codeBatch.setCreateEmpId(this.loginInfo.getId());
            codeBatch.setCreateEmp(this.loginInfo.getName());
            codeBatch.setUpdateEmpId(this.loginInfo.getId());
            codeBatch.setUpdateEmp(this.loginInfo.getName());
            this.addCodeBatchList.add(codeBatch);
            this.codeBatchId = codeBatch.getId();
            codeBatchGoodstMap.put("code_batch", codeBatch);
        } else {
            if (CollectionUtil.isNotEmpty(this.addCodeBatchList)) {
                for (ZjCodeBatch batch : this.addCodeBatchList) {
                    batch.setQty(batch.getQty() +1);
                    codeBatchGoodstMap.put("code_batch", batch);
                }
            } else {
                codeBatch.setQty(codeBatch.getQty() +1);
                if (CollectionUtil.isNotEmpty(this.updateCodeBatchList) && this.updateCodeBatchList.size() > 0) {
                    this.updateCodeBatchList.get(0).setQty(codeBatch.getQty());
                } else {
                    this.updateCodeBatchList.add(codeBatch);
                }
                codeBatchGoodstMap.put("code_batch", codeBatch);
            }
        }
        List<ZjCodeBatchGoods> codeBatchGoodsList = JSON.parseArray(JSON.toJSONString(codeBatchGoodstMap.get("code_batch_goods")), ZjCodeBatchGoods.class);
        if (null == codeBatchGoodsList) {
            codeBatchGoodsList = new ArrayList<>();
        }
        ZjCodeBatchGoods codeBatchGoods = codeBatchGoodsList.stream().filter(f -> Objects.equals(f.getGoodsId(), viewGoods.getId()) && Objects.equals(f.getGoodsSpecId(), viewGoods.getSpecId())).findFirst().orElse(null);;
        if (null == codeBatchGoods) {
            codeBatchGoods = new ZjCodeBatchGoods();
            codeBatchGoods.setId(IdWorker.getId());
            codeBatchGoods.setCustAccId(this.loginInfo.getCustAccId());
            codeBatchGoods.setBatchId(this.codeBatchId);
            codeBatchGoods.setGoodsId(viewGoods.getId());
            codeBatchGoods.setGoodsSpecId(viewGoods.getSpecId());
            codeBatchGoods.setQty(1);
            codeBatchGoods.setWarrantyDay(null != codeExcelImport.getWarrantyDay()? codeExcelImport.getWarrantyDay() : viewGoods.getWarrantyDay());
            codeBatchGoodsList.add(codeBatchGoods);
            this.addCodeBatchGoodsList.add(codeBatchGoods);
        } else {
            for (ZjCodeBatchGoods goods : codeBatchGoodsList) {
                if (Objects.equals(goods.getGoodsId(), codeBatchGoods.getGoodsId()) && Objects.equals(goods.getGoodsSpecId(), codeBatchGoods.getGoodsSpecId())) {
                    goods.setQty(goods.getQty() +1);
                    break;
                }
            }
            if (CollectionUtil.isNotEmpty(this.addCodeBatchGoodsList) && this.addCodeBatchGoodsList.stream().anyMatch(f -> Objects.equals(f.getGoodsId(), viewGoods.getId()) &&
                    Objects.equals(f.getGoodsSpecId(), viewGoods.getSpecId()))) {
                for (ZjCodeBatchGoods goods : this.addCodeBatchGoodsList) {
                    if (Objects.equals(goods.getGoodsId(), codeBatchGoods.getGoodsId()) && Objects.equals(goods.getGoodsSpecId(), codeBatchGoods.getGoodsSpecId())) {
                        goods.setQty(goods.getQty() +1);
                        break;
                    }
                }
            } else {
                if (CollectionUtil.isNotEmpty(this.updateCodeBatchGoodsList) && this.updateCodeBatchGoodsList.stream().anyMatch(f -> Objects.equals(f.getGoodsId(), viewGoods.getId()) &&
                        Objects.equals(f.getGoodsSpecId(), viewGoods.getSpecId()))) {
                    for (ZjCodeBatchGoods goods : this.updateCodeBatchGoodsList) {
                        if (Objects.equals(goods.getGoodsId(), codeBatchGoods.getGoodsId()) && Objects.equals(goods.getGoodsSpecId(), codeBatchGoods.getGoodsSpecId())) {
                            goods.setQty(goods.getQty() +1);
                            break;
                        }
                    }
                } else {
                    codeBatchGoods.setQty(codeBatchGoods.getQty());
                    this.updateCodeBatchGoodsList.add(codeBatchGoods);
                }
            }
        }
        codeBatchGoodstMap.put("code_batch_goods", codeBatchGoodsList);
        this.codebatchGoodsId = codeBatchGoods.getId();
        RedisUtil.setCache(code_batch_goods_data_key, JSON.toJSONString(codeBatchGoodstMap));
    }

    /**
     * 编码数据
     * @param viewGoods
     * @param codeExcelImport
     */
    private void setCode(ViewGoods viewGoods, CodeExcelImport codeExcelImport) {
        ZjCode addCode = new ZjCode();
        addCode.setId(IdWorker.getId());
        addCode.setCustAccId(this.loginInfo.getCustAccId());
        addCode.setBatchId(this.codeBatchId);
        addCode.setBatchGoodsId(this.codebatchGoodsId);
        addCode.setType(codeExcelImport.getType());
        addCode.setGoodsId(viewGoods.getId());
        addCode.setGoodsSpecId(viewGoods.getSpecId());
        if (StringUtils.isBlank(codeExcelImport.getCode())) {
            addCode.setSecurityCode((StringUtils.isBlank(this.codeSet.getCodePrefix()) ? "" :
                    this.codeSet.getCodePrefix()) + CodeUtil.generatorCode(this.codeSet.getCodeRule(), viewGoods.getCreateTime()));
        } else {
            addCode.setSecurityCode(codeExcelImport.getCode());
        }
        addCode.setStoreCode((StringUtils.isBlank(this.codeSet.getStorehousePrefix())? "" :
                this.codeSet.getStorehousePrefix()) + CodeUtil.generatorCode(this.codeSet.getStorehouseRule(), viewGoods.getCreateTime()));
        addCode.setWarrantyDay(null != codeExcelImport.getWarrantyDay()? codeExcelImport.getWarrantyDay() : viewGoods.getWarrantyDay());
        addCode.setStatus(CodeConstant.CODE_STATUS_1);
        addCode.setActiva(null != codeExcelImport.getActiva() && codeExcelImport.getActiva());
        addCode.setActivaTime(addCode.getActiva()? new Date() : null);
        addCode.setStoreId(0L);
        addCode.setStorePositionId(0L);
        addCode.setStoreStatus(CodeConstant.STORE_STATUS_1);
        addCode.setOutStoreActiva(false);
        addCode.setOutStoreWarranty(false);
        addCode.setCodeSource(CodeConstant.CODE_SOURCE_2);
        addCode.setNetworkId(0L);
        addCode.setNetwork("");
        addCode.setDisabled(false);
        addCode.setCreateEmpId(this.loginInfo.getId());
        addCode.setCreateEmp(this.loginInfo.getName());
        addCode.setUpdateEmpId(this.loginInfo.getId());
        addCode.setUpdateEmp(this.loginInfo.getName());
        this.codeList.add(addCode);

        ZjCodeInfo codeInfo = new ZjCodeInfo();
        codeInfo.setId(addCode.getId());
        codeInfo.setCustAccId(this.loginInfo.getCustAccId());
        codeInfo.setRegister(false);
        codeInfo.setSelectNum(0);
        codeInfo.setInboundCode("");
        codeInfo.setOutboundCode("");
        this.codeInfoList.add(codeInfo);
        this.codeLogList.add(new CodeLogExtend(this.loginInfo, CodeConstant.CODE_LOG_TYPE_1, addCode.getId(), "Excel导入并创建一物一码", ""));
    }

    /**
     * 获取缓存的数据
     * @return
     */
    private Map<String, Object> getCacheData() {
        Map<String, Object> cacheData = null;
        Object byKey = RedisUtil.getByKey(this.CACHE_IMPORT_DATA_REDIS_KEY);
        if (null == byKey) {
            return new HashMap<>();
        }
        cacheData = JSON.parseObject((String) byKey, Map.class);
        return cacheData;
    }


    /**
     * 校验数据是否重复
     * @param rowNo
     * @param codeExcelImport
     * @return
     */
    private boolean checkDataRepeat(int rowNo, CodeExcelImport codeExcelImport) {
        boolean isDataRepeat = false;
        Object byKey = RedisUtil.getByKey(this.check_data_repeat_key);
        Map<String, Object> dataRepeatMap = JSON.parseObject((String) byKey, HashMap.class);
        if (null == dataRepeatMap) {
            dataRepeatMap = new HashMap<>();
        }
        // 商品一物一码
        if (StringUtils.isNotBlank(codeExcelImport.getCode())) {
            if (null != dataRepeatMap.get("goods_code_" + codeExcelImport.getCode())) {
                this.message.append("A").append(rowNo).append("单元 商品一物一码已重复填写").append("</br>");
                isDataRepeat = true;
            } else {
                dataRepeatMap.put("goods_code_" + codeExcelImport.getCode(), true);
            }
            RedisUtil.setCache(this.check_data_repeat_key, JSON.toJSONString(dataRepeatMap));
        }
        if (isDataRepeat) {
            this.importFailt.add(codeExcelImport);
        }
        return isDataRepeat;
    }

    /**
     * 校验数据空值&长度
     * @param rowNo
     * @param codeExcelImport
     * @return
     */
    private boolean checkEmpty(int rowNo, CodeExcelImport codeExcelImport) {
        if (StringUtils.isNotBlank(codeExcelImport.getCode()) && codeExcelImport.getCode().length() > 60) {
            this.importFailt.add(codeExcelImport);
            message.append("A").append(rowNo).append("单元 一物一码/SN码字符长度限制60个以内").append("</br>");
            return true;
        }
        if (null == codeExcelImport.getType()) {
            this.importFailt.add(codeExcelImport);
            message.append("B").append(rowNo).append("单元 编码类型不能为空").append("</br>");
            return true;
        }
        if (!Pattern.matches("^(1|2)$", codeExcelImport.getType().toString())) {
            this.importFailt.add(codeExcelImport);
            message.append("B").append(rowNo).append("单元 编码类型参数不合法").append("</br>");
            return true;
        }
        if (StringUtils.isBlank(codeExcelImport.getGoodsModel())) {
            this.importFailt.add(codeExcelImport);
            message.append("E").append(rowNo).append("单元 商品型号不能为空").append("</br>");
            return true;
        }
        if (StringUtils.isBlank(codeExcelImport.getGoodsSpec())) {
            this.importFailt.add(codeExcelImport);
            message.append("F").append(rowNo).append("单元 商品规格不能为空").append("</br>");
            return true;
        }
        return false;
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        RedisUtil.clearCache(this.check_data_repeat_key);
        RedisUtil.clearCache(this.code_batch_goods_data_key);
        if (CollectionUtil.isNotEmpty(this.codeList)) {
            excuteSave();
        }
        if (!this.isEndSend) {
            ProgressWebSocketServer.send(this.progressUuid, true, 100, this.totalCount, this.importFailt, this.message.toString());
            this.isEndSend = true;
        }
    }

    private void excuteSave() {
        String color = "#67C23A";
        this.batch_no++;
        this.message.append("<div style=color:").append(color).append(">解析完成，正在处理保存 [第 ").append(this.batch_no).append("批] 数据，请耐心等待...</div>");
        sendMessage();
        this.message.append("<div style=color:").append(color).append(">第 ").append(this.batch_no);
        TransactionStatus status = this.transactionManager.getTransaction(TransactionUtil.transactionDefinition());
        try {
            this.codeBatchService.saveExcelBatchCode(this.addCodeBatchList, this.updateCodeBatchList, this.addCodeBatchGoodsList, this.updateCodeBatchGoodsList);
            this.codeService.saveOrderExcelImportCode(this.codeList, null, this.codeInfoList, null, this.codeLogList);
            this.transactionManager.commit(status);
            this.message.append(" 批导入成功</div>");
        }catch (Exception e) {
            e.printStackTrace();
            color = "#F56C6C";
            logger.error("一物一码数据第 {} 批导入失败，失败原因：{}", this.batch_no, e.getMessage());
            this.message.append(" 导入失败！原因：").append(e.getMessage()).append("</div>");
            this.transactionManager.rollback(status);
        }
        initData();
    }

    /**
     * 发送进度消息
     */
    private void sendMessage() {
        ProgressWebSocketServer.send(this.progressUuid, false, (1.0 * this.currentExcuteCount / this.excelTotalRowCount *100), this.importFailt, this.message.toString());
    }
}
